#include "lang.h"

static std::vector<std::string> propertylessTypes;

static void outputGCMarkProps(FILE *f, const struct TreeType &t)
{
    if (t.name == "CHAIN_HEAD")
        return;

    if (t.props.size() == 0) {
        propertylessTypes.push_back(t.name);
        return;
    }

    fprintf(f, "    case %s:\n", t.name.c_str());

    for (const auto prop : t.props)
        if (prop.second.baseType.isTree)
            fprintf(f, "        mark_tree(%s(t));\n", t.getPropAccessor(prop.first).c_str());

    fputs("        break;\n", f);
}

static void outputPropertylessTypes(FILE *f)
{
    fputs("    /* These types have no properties to traverse */\n", f);
    for (const auto prop : propertylessTypes)
        fprintf(f, "    case %s:\n", prop.c_str());

    fputs("        return;\n", f);
}

static void outputGCPropTraverse(const struct lang &lang,
                                 FILE *f)
{
    for (const auto type : lang.treeTypes)
        outputGCMarkProps(f, type);

    for (const auto ctype : lang.treeCTypes)
        outputGCMarkProps(f, ctype);

    outputPropertylessTypes(f);
}

static void outputPreamble(FILE *f)
{
    fputs("\n"
          "#include \"gc-internal.h\"\n"
          "\n"
          "void mark_object(gc_obj obj)\n"
          "{\n"
          "    tree t;\n"
          "\n"
          "    if (!obj)\n"
          "        return;\n"
          "\n"
          "    obj->reachable = 1;\n"
          "\n"
          "    t = &obj->t;\n"
          "\n"
          "    mark_tree(tLOCUS(t).file);\n"
          "\n"
          "    switch (TYPE(t)) {\n"
          "    case CHAIN_HEAD:\n"
          "    {\n"
          "        tree i;\n"
          "        for_each_tree(i, t)\n"
          "            mark_tree(i);\n"
          "    }\n"
          "    break;\n", f);
}

static void outputEpilogue(FILE *f)
{
    fputs("    }\n"
          "}\n", f);
}


static void usage(char *progname)
{
    fprintf(stderr, "%s - Generate the `tree-access.h' file from a language description\n",
            progname);
    fprintf(stderr, "Usage: %s LANG_DESC_FILE\n", progname);
    fprintf(stderr, "\n");
    fprintf(stderr, "Where LANG_DESC_FILE is a language description file.\n");
}

int main(int argc, char *argv[])
{
    struct lang lang;
    FILE *f;

    if (argc != 2) {
        fprintf(stderr, "Error: %s expects exactly one argument\n", argv[0]);
        usage(argv[0]);
        return 1;
    }

    f = fopen(argv[1], "r");

    if (!f) {
        perror("Could not open lang file");
        usage(argv[0]);
        exit(2);
    }

    lang_read(f, lang);

    FILE *output = fopen("gc-internal.c", "w");

    fputs("/*\n"
          " * gc-internal.c - Define the mark_object function.\n"
          " *\n"
          " * Automatically generated by gengc. DO NOT EDIT.\n"
          " */\n\n", output);

    outputPreamble(output);
    outputGCPropTraverse(lang, output);
    outputEpilogue(output);

    fclose(output);

    return 0;
}
